﻿// /////////////////////////////////////////////////////////////////////
// 
//  Codeplex JSAnalyse
//  Copyright (c) 2011
//  JSAnalyse.Core   StatementVisitor.cs
// 
// /////////////////////////////////////////////////////////////////////
using System.Collections.Generic;
using Jint.Debugger;
using Jint.Expressions;

namespace JSAnalyse.Core.Parser
{
    internal class StatementVisitor : IStatementVisitor
    {
        #region Private Variables
        private List<DeclarationInfo> _memberExpressions = new List<DeclarationInfo>();
        private List<DeclarationInfo> _declarations = new List<DeclarationInfo>();
        #endregion

        #region Public properties
        public List<DeclarationInfo> MemberExpressions
        {
            get
            {
                return _memberExpressions;
            }
        }
        public List<DeclarationInfo> Declarations
        {
            get
            {
                return _declarations;
            }
        }

        private bool IsGlobalContext 
        { 
            get; 
            set; 
        }
        private bool IsAssignment 
        { 
            get; 
            set; 
        }
        private SourceCodeDescriptor LastSourceCode 
        { 
            get; 
            set; 
        }

        #endregion

        #region Visitor Interface Members
        public void Visit(Statement expression)
        {
            SetSourceCode(expression);
            // Finished
        }

        public void Visit(RegexpExpression expression)
        {
            SetSourceCode(expression);
            // Finished
        }

        public void Visit(ValueExpression expression)
        {
            SetSourceCode(expression);
            // Finished
        }

        public void Visit(UnaryExpression expression)
        {
            SetSourceCode(expression);
            expression.Expression.Accept(this);
        }

        public void Visit(TernaryExpression expression)
        {
            SetSourceCode(expression);
            expression.LeftExpression.Accept(this);
            expression.MiddleExpression.Accept(this);
            expression.RightExpression.Accept(this);
        }

        public void Visit(BinaryExpression expression)
        {
            SetSourceCode(expression);
            expression.LeftExpression.Accept(this);
            expression.RightExpression.Accept(this);
        }

        public void Visit(NewExpression expression)
        {
            SetSourceCode(expression);
            foreach (var arg in expression.Arguments)
            {
                arg.Accept(this);
            }
            foreach (var gen in expression.Generics)
            {
                gen.Accept(this);
            }
            expression.Expression.Accept(this);
        }

        public void Visit(JsonExpression expression)
        {
            SetSourceCode(expression);
            foreach (var value in expression.Values)
            {
                value.Value.Accept(this);
            }
        }

        public void Visit(Identifier expression)
        {
            SetSourceCode(expression);
            // Finished
        }

        public void Visit(PropertyDeclarationExpression expression)
        {
            SetSourceCode(expression);

            var globalContext = IsGlobalContext;
            IsGlobalContext = false;

            if (expression.Expression != null)
            {
                expression.Expression.Accept(this);
            }

            IsGlobalContext = globalContext;
        }

        public void Visit(PropertyExpression expression)
        {
            SetSourceCode(expression);
            // Finished
        }

        public void Visit(Indexer expression)
        {
            SetSourceCode(expression);
            // Finished
        }

        public void Visit(MethodCall expression)
        {
            SetSourceCode(expression);
            foreach (var arg in expression.Arguments)
            {
                arg.Accept(this);
            }
            foreach (var gen in expression.Generics)
            {
                gen.Accept(this);
            }
        }

        public void Visit(MemberExpression expression)
        {
            SetSourceCode(expression);
            bool foundMember = CheckMemberExpression(expression);

            var globalContext = IsGlobalContext;
            IsGlobalContext = false;

            if (IsAssignment && expression.Previous != null && expression.Previous.ToString() == Keyword.Window)
            {
                IsGlobalContext = true;
                if (expression.Member is PropertyExpression)
                {
                    CheckPropertyDeclarationExpression(expression.Member as PropertyExpression);
                }
            }

            if (expression.Member != null)
            {
                expression.Member.Accept(this);
            }

            if (expression.Previous != null && !foundMember)
            {
                expression.Previous.Accept(this);
            }
            IsGlobalContext = globalContext;
        }

        public void Visit(FunctionExpression expression)
        {
            SetSourceCode(expression);
            expression.Statement.Accept(this);
        }

        public void Visit(CommaOperatorStatement expression)
        {
            SetSourceCode(expression);
            foreach (var statement in expression.Statements)
            {
                statement.Accept(this);
            }
        }

        public void Visit(ArrayDeclaration expression)
        {
            SetSourceCode(expression);
            foreach (var parameter in expression.Parameters)
            {
                parameter.Accept(this);
            }
        }

        public void Visit(WhileStatement expression)
        {
            SetSourceCode(expression);
            expression.Condition.Accept(this);
            expression.Statement.Accept(this);
        }

        public void Visit(VariableDeclarationStatement expression)
        {
            SetSourceCode(expression);
            CheckVariableDeclarationStatement(expression);
            if (expression.Expression != null)
            {
                expression.Expression.Accept(this);
            }
        }

        public void Visit(TryStatement expression)
        {
            SetSourceCode(expression);
            expression.Catch.Statement.Accept(this);
            if (expression.Finally != null)
            {
                expression.Finally.Statement.Accept(this);
            }
            expression.Statement.Accept(this);
        }

        public void Visit(ThrowStatement expression)
        {
            SetSourceCode(expression);
            expression.Expression.Accept(this);
        }

        public void Visit(WithStatement expression)
        {
            SetSourceCode(expression);
            expression.Expression.Accept(this);
            expression.Statement.Accept(this);
        }

        public void Visit(SwitchStatement expression)
        {
            SetSourceCode(expression);
            foreach (var caseClause in expression.CaseClauses)
            {
                caseClause.Expression.Accept(this);
                caseClause.Statements.Accept(this);
            }
            if (expression.DefaultStatements != null)
            {
                expression.DefaultStatements.Accept(this);
            }
            expression.Expression.Accept(this);
        }

        public void Visit(ReturnStatement expression)
        {
            SetSourceCode(expression);
            if (expression.Expression != null)
            {
                expression.Expression.Accept(this);
            }
        }

        public void Visit(IfStatement expression)
        {
            SetSourceCode(expression);
            expression.Expression.Accept(this);
            expression.Then.Accept(this);
            if (expression.Else != null)
            {
                expression.Else.Accept(this);
            }
        }

        public void Visit(FunctionDeclarationStatement expression)
        {
            SetSourceCode(expression);
            CheckFunctionDeclarationStatement(expression);
            expression.Statement.Accept(this);
        }

        public void Visit(ForStatement expression)
        {
            SetSourceCode(expression);
            expression.Statement.Accept(this);
        }

        public void Visit(ForEachInStatement expression)
        {
            SetSourceCode(expression);
            expression.Statement.Accept(this);
        }

        public void Visit(ExpressionStatement expression)
        {
            SetSourceCode(expression);
            expression.Expression.Accept(this);
        }

        public void Visit(EmptyStatement expression)
        {
            SetSourceCode(expression);
        }

        public void Visit(DoWhileStatement expression)
        {
            SetSourceCode(expression);
            expression.Condition.Accept(this);
            expression.Statement.Accept(this);
        }

        public void Visit(ContinueStatement expression)
        {
            SetSourceCode(expression);
        }

        public void Visit(BreakStatement expression)
        {
            SetSourceCode(expression);
        }

        public void Visit(BlockStatement expression)
        {
            SetSourceCode(expression);
            foreach (var statement in expression.Statements)
            {
                statement.Accept(this);
            }
        }

        public void Visit(AssignmentExpression expression)
        {
            CheckAssignmentExpression(expression);
            
            var globalContext = IsGlobalContext;
            IsGlobalContext = false;

            SetSourceCode(expression);
            IsAssignment = true;
            expression.Left.Accept(this);
            IsAssignment = false;
            expression.Right.Accept(this);
            IsGlobalContext = globalContext;
        }

        public void Visit(Program expression)
        {
            IsGlobalContext = true;
            SetSourceCode(expression);
            foreach (var statement in expression.Statements)
            {
                statement.Accept(this);
            }
        }
        #endregion

        #region Helper methods
        private void SetSourceCode(Statement expression)
        {
            if (expression != null && expression.Source != null)
            {
                LastSourceCode = expression.Source;
            }
        }
        private bool CheckMemberExpression(MemberExpression expression)
        {
            var result = false;
            var subMemberExpression = expression.Previous as MemberExpression;
            if (subMemberExpression != null)
            {
                expression = subMemberExpression;
                if (subMemberExpression.Source != null)
                {
                    SetSourceCode(subMemberExpression);
                }
            }

            var identifier = GetIdentifier(expression);
            if (identifier != null)
            {
                AddToCallInfoList(identifier, LastSourceCode, MemberExpressions);

                result = true;
            }
            return result;
        }

        private void CheckVariableDeclarationStatement(VariableDeclarationStatement statement)
        {
            var identifier = new IdentifierInfo(statement.Identifier);
            if (statement.Global || IsGlobalContext)
            {
                AddToCallInfoList(identifier, LastSourceCode, Declarations);
            }
        }
        private void CheckPropertyDeclarationExpression(PropertyExpression expression)
        {
            if (expression != null)
            {
                var identifier = new IdentifierInfo(expression.ToString());
                if (IsGlobalContext)
                {
                    AddToCallInfoList(identifier, LastSourceCode, Declarations);
                }
            }
        }
        private void CheckFunctionDeclarationStatement(FunctionDeclarationStatement expression)
        {
            if (expression != null)
            {
                var identifier = new IdentifierInfo(expression.Name);
                if (IsGlobalContext)
                {
                    AddToCallInfoList(identifier, LastSourceCode, Declarations);
                }
            }
        }
        private void CheckAssignmentExpression(AssignmentExpression expression)
        {
			if (expression != null)
            {
	            var memberExpression = expression.Left as MemberExpression;
                if (memberExpression != null)
                {
                    var classIdentifier = memberExpression.Previous.ToString();
                    var subIdentifier = memberExpression.Member.ToString();

                    if (IsGlobalContext)
	                {
                        AddToCallInfoList(new IdentifierInfo(classIdentifier, subIdentifier), LastSourceCode, Declarations);
	                }
                }
			}
        }
        private void AddToCallInfoList(IdentifierInfo identifier, SourceCodeDescriptor sourceCode, List<DeclarationInfo> callInfos)
        {
            if (!callInfos.Exists(item => item.Identifier.ClassName == identifier.ClassName && item.Identifier.Member == identifier.Member))
            {
                callInfos.Add(new DeclarationInfo(identifier, GetLine(sourceCode), GetCharacter(sourceCode)));
            }
        }

        /// <summary>
        /// Gets the position of first character in source code
        /// </summary>
        /// <param name="sourceCodeDescriptor">The source code descriptor.</param>
        /// <returns>position of first character in source code</returns>
        private int GetCharacter(SourceCodeDescriptor sourceCodeDescriptor)
        {
            return GetLocation(sourceCodeDescriptor).Char;
        }

        /// <summary>
        /// Gets the line of the first character in source code
        /// </summary>
        /// <param name="sourceCodeDescriptor">The source code descriptor.</param>
        /// <returns>first character in source code</returns>
        private int GetLine(SourceCodeDescriptor sourceCodeDescriptor)
        {
            return GetLocation(sourceCodeDescriptor).Line;
        }

        /// <summary>
        /// Gets the location from given sourcecodedescriptor. If null default location is created.
        /// </summary>
        /// <param name="sourceCodeDescriptor">The source code descriptor.</param>
        /// <returns>location from given sourcecodedescriptor or default location if null</returns>
        private SourceCodeDescriptor.Location GetLocation(SourceCodeDescriptor sourceCodeDescriptor)
        {
            return sourceCodeDescriptor != null && sourceCodeDescriptor.Start != null ? sourceCodeDescriptor.Start : new SourceCodeDescriptor.Location(-1, -1);
        }

        /// <summary>
        /// Gets the identifier of the given expression
        /// </summary>
        /// <param name="expression">The expression to get identifier from</param>
        /// <returns>identifier</returns>
        private IdentifierInfo GetIdentifier(MemberExpression expression)
        {
            IdentifierInfo result = null;

            if (expression != null)
            {
                string classIdentifier = GetIdentifierName(expression.Previous);
                string subIdentifier = GetIdentifierName(expression.Member);

                if (!string.IsNullOrEmpty(classIdentifier))
                {
                    result = new IdentifierInfo(classIdentifier, subIdentifier);
                }
            }

            return result;
        }

        private string GetIdentifierName(Expression expression)
        {
            string result = null;
            if (expression is Identifier || expression is PropertyExpression)
            {
                result = expression.ToString();
            }
            return result;
        }

        #endregion
    }
}
